/* vim: set ai et ts=4 sw=4: */
#include "LCD_st7735.h"
#include "DEV_Config.h"

#define DELAY 0x80

// based on Adafruit ST7735 library for Arduino
static const uint8_t init_cmds1[] =
    {       // Init for 7735R, part 1 (red or green tab)
        15, // 15 commands in list:
        ST7735_SWRESET,
        DELAY, //  1: Software reset, 0 args, w/delay
        150,   //     150 ms delay
        ST7735_SLPOUT,
        DELAY, //  2: Out of sleep mode, 0 args, w/delay
        255,   //     500 ms delay
        ST7735_FRMCTR1,
        3, //  3: Frame rate ctrl - normal mode, 3 args:
        0x01,
        0x2C,
        0x2D, //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
        ST7735_FRMCTR2,
        3, //  4: Frame rate control - idle mode, 3 args:
        0x01,
        0x2C,
        0x2D, //     Rate = fosc/(1x2+40) * (LINE+2C+2D)
        ST7735_FRMCTR3,
        6, //  5: Frame rate ctrl - partial mode, 6 args:
        0x01,
        0x2C,
        0x2D, //     Dot inversion mode
        0x01,
        0x2C,
        0x2D, //     Line inversion mode
        ST7735_INVCTR,
        1,    //  6: Display inversion ctrl, 1 arg, no delay:
        0x07, //     No inversion
        ST7735_PWCTR1,
        3, //  7: Power control, 3 args, no delay:
        0xA2,
        0x02, //     -4.6V
        0x84, //     AUTO mode
        ST7735_PWCTR2,
        1,    //  8: Power control, 1 arg, no delay:
        0xC5, //     VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD
        ST7735_PWCTR3,
        2,    //  9: Power control, 2 args, no delay:
        0x0A, //     Opamp current small
        0x00, //     Boost frequency
        ST7735_PWCTR4,
        2,    // 10: Power control, 2 args, no delay:
        0x8A, //     BCLK/2, Opamp current small & Medium low
        0x2A,
        ST7735_PWCTR5,
        2, // 11: Power control, 2 args, no delay:
        0x8A,
        0xEE,
        ST7735_VMCTR1,
        1, // 12: Power control, 1 arg, no delay:
        0x0E,
        ST7735_INVOFF,
        0, // 13: Don't invert display, no args, no delay
        ST7735_MADCTL,
        1,               // 14: Memory access control (directions), 1 arg:
        ST7735_ROTATION, //     row addr/col addr, bottom to top refresh
        ST7735_COLMOD,
        1,     // 15: set color mode, 1 arg, no delay:
        0x05}, //     16-bit color

#if (defined(ST7735_IS_128X128) || defined(ST7735_IS_160X128))
    init_cmds2[] =
        {      // Init for 7735R, part 2 (1.44" display)
            2, //  2 commands in list:
            ST7735_CASET,
            4, //  1: Column addr set, 4 args, no delay:
            0x00,
            0x00, //     XSTART = 0
            0x00,
            0x7F, //     XEND = 127
            ST7735_RASET,
            4, //  2: Row addr set, 4 args, no delay:
            0x00,
            0x00, //     XSTART = 0
            0x00,
            0x7F}, //     XEND = 127
#endif             // ST7735_IS_128X128

#ifdef ST7735_IS_160X80
    init_cmds2[] =
        {
            // Init for 7735S, part 2 (160x80 display)
            2, //  2 commands in list:
            ST7735_CASET,
            4, //  1: Column addr set, 4 args, no delay:
            0x00,
            0x00, //     XSTART = 0
            0x00,
            0x4F, //     XEND = 79
            ST7735_RASET,
            4, //  2: Row addr set, 4 args, no delay:
            0x00,
            0x00, //     XSTART = 0
            0x00,
            0x9F, //     XEND = 159
},                //  3: Invert colors
#endif

    init_cmds3[] = { // Init for 7735R, part 3 (red or green tab)
        4,           //  4 commands in list:
        ST7735_GMCTRP1,
        16, //  1: Magical unicorn dust, 16 args, no delay:
        0x02,
        0x1c,
        0x07,
        0x12,
        0x37,
        0x32,
        0x29,
        0x2d,
        0x29,
        0x25,
        0x2B,
        0x39,
        0x00,
        0x01,
        0x03,
        0x10,
        ST7735_GMCTRN1,
        16, //  2: Sparkles and rainbows, 16 args, no delay:
        0x03,
        0x1d,
        0x07,
        0x06,
        0x2E,
        0x2C,
        0x29,
        0x2D,
        0x2E,
        0x2E,
        0x37,
        0x3F,
        0x00,
        0x00,
        0x02,
        0x10,
        ST7735_NORON,
        DELAY, //  3: Normal display on, no args, w/delay
        10,    //     10 ms delay
        ST7735_DISPON,
        DELAY, //  4: Main screen turn on, no args w/delay
        100};  //     100 ms delay

static void ST7735_Select() {
  // HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
  DEV_Digital_Write(EPD_CS_PIN, 0);
}

void ST7735_Unselect() {
  // HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_SET);
  DEV_Digital_Write(EPD_CS_PIN, 1);
}

static void ST7735_Reset() {
  DEV_Digital_Write(EPD_RST_PIN, 0);
  sleep_ms(5);
  DEV_Digital_Write(EPD_RST_PIN, 1);
}

static void ST7735_WriteCommand(uint8_t cmd) {
  // HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_RESET);
  DEV_Digital_Write(EPD_DC_PIN, 0);
  spi_write_blocking(SPI_PORT, &cmd, sizeof(cmd));
  // HAL_SPI_Transmit(&ST7735_SPI_PORT, &cmd, sizeof(cmd), HAL_MAX_DELAY);
}

static void ST7735_WriteData(uint8_t *buff, size_t buff_size) {
  // HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
  DEV_Digital_Write(EPD_DC_PIN, 1);
  spi_write_blocking(SPI_PORT, buff, buff_size);
  // HAL_SPI_Transmit(&ST7735_SPI_PORT, buff, buff_size, HAL_MAX_DELAY);
}

static void ST7735_ExecuteCommandList(const uint8_t *addr) {
  uint8_t numCommands, numArgs;
  uint16_t ms;

  numCommands = *addr++;
  while (numCommands--) {
    uint8_t cmd = *addr++;
    ST7735_WriteCommand(cmd);

    numArgs = *addr++;
    // If high bit set, delay follows args
    ms = numArgs & DELAY;
    numArgs &= ~DELAY;
    if (numArgs) {
      ST7735_WriteData((uint8_t *)addr, numArgs);
      addr += numArgs;
    }

    if (ms) {
      ms = *addr++;
      if (ms == 255)
        ms = 500;
      sleep_ms(ms);
    }
  }
}

static void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1,
                                    uint8_t y1) {
  // column address set
  ST7735_WriteCommand(ST7735_CASET);
  uint8_t data[] = {0x00, x0 + ST7735_XSTART, 0x00, x1 + ST7735_XSTART};
  ST7735_WriteData(data, sizeof(data));

  // row address set
  ST7735_WriteCommand(ST7735_RASET);
  data[1] = y0 + ST7735_YSTART;
  data[3] = y1 + ST7735_YSTART;
  ST7735_WriteData(data, sizeof(data));

  // write to RAM
  ST7735_WriteCommand(ST7735_RAMWR);
}

void ST7735_Init() {
  DEV_Module_Init();
  ST7735_Reset();
  ST7735_Select();
  ST7735_ExecuteCommandList(init_cmds1);
  ST7735_ExecuteCommandList(init_cmds2);
  ST7735_ExecuteCommandList(init_cmds3);
  ST7735_Unselect();
}

void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
  if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT))
    return;

  ST7735_Select();

  ST7735_SetAddressWindow(x, y, x + 1, y + 1);
  uint8_t data[] = {color >> 8, color & 0xFF};
  ST7735_WriteData(data, sizeof(data));

  ST7735_Unselect();
}

static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font,
                             uint16_t color, uint16_t bgcolor) {
  uint32_t i, b, j;

  ST7735_SetAddressWindow(x, y, x + font.width - 1, y + font.height - 1);

  for (i = 0; i < font.height; i++) {
    b = font.data[(ch - 32) * font.height + i];
    for (j = 0; j < font.width; j++) {
      if ((b << j) & 0x8000) {
        uint8_t data[] = {color >> 8, color & 0xFF};
        ST7735_WriteData(data, sizeof(data));
      } else {
        uint8_t data[] = {bgcolor >> 8, bgcolor & 0xFF};
        ST7735_WriteData(data, sizeof(data));
      }
    }
  }
}

/*
Simpler (and probably slower) implementation:

static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font,
uint16_t color) { uint32_t i, b, j;

    for(i = 0; i < font.height; i++) {
        b = font.data[(ch - 32) * font.height + i];
        for(j = 0; j < font.width; j++) {
            if((b << j) & 0x8000)  {
                ST7735_DrawPixel(x + j, y + i, color);
            }
        }
    }
}
*/

void ST7735_WriteString(uint16_t x, uint16_t y, const char *str, FontDef font,
                        uint16_t color, uint16_t bgcolor) {
  ST7735_Select();

  while (*str) {
    if (x + font.width >= ST7735_WIDTH) {
      x = 0;
      y += font.height;
      if (y + font.height >= ST7735_HEIGHT) {
        break;
      }

      if (*str == ' ') {
        // skip spaces in the beginning of the new line
        str++;
        continue;
      }
    }

    ST7735_WriteChar(x, y, *str, font, color, bgcolor);
    x += font.width;
    str++;
  }

  ST7735_Unselect();
}

void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
                          uint16_t color) {
  // clipping
  if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT))
    return;
  if ((x + w - 1) >= ST7735_WIDTH)
    w = ST7735_WIDTH - x;
  if ((y + h - 1) >= ST7735_HEIGHT)
    h = ST7735_HEIGHT - y;

  ST7735_Select();
  ST7735_SetAddressWindow(x, y, x + w - 1, y + h - 1);

  uint8_t data[] = {color >> 8, color & 0xFF};
  // HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);

  DEV_Digital_Write(EPD_DC_PIN, 1);
  for (y = h; y > 0; y--) {
    for (x = w; x > 0; x--) {
      // HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), HAL_MAX_DELAY);
      spi_write_blocking(SPI_PORT, data, sizeof(data));
    }
  }

  ST7735_Unselect();
}

void ST7735_FillScreen(uint16_t color) {
  ST7735_FillRectangle(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color);
}

void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
                      const uint8_t *data) {
  ST7735_Select();
  ST7735_SetAddressWindow(x, y, x + w - 1, y + h - 1);
  ST7735_WriteData((uint8_t *)data, sizeof(uint16_t) * w * h);
  ST7735_Unselect();
}

void ST7735_InvertColors(bool invert) {
  ST7735_Select();
  ST7735_WriteCommand(invert ? ST7735_INVON : ST7735_INVOFF);
  ST7735_Unselect();
}
